/**
 * @file filter
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import {Renderer, render} from '../../factory';
import {isEmptyObject} from '../../util';
import * as cx from 'classnames';
import * as assign from 'object-assign';
import {confirm} from '../../../widgets/alert/alert';
import './combo-form';
import {Enhance as FormItem} from '../form-item';
import * as isPlainObject from 'lodash/isPlainObject';
import { DropTarget} from 'react-dnd';
import * as update from 'react-addons-update';
import './combo.scss';
import {findByAttrValue} from '../../../util/helper';
import {toBol} from '../../util';
import {bool} from '../../propTypes';


const CARD_TYPE = 'combo';
const filterOptions = (options, value, name, component, ignoreSelf = false) => {
  if (!options || !value || !Array.isArray(value)) {
    return options;
  }

  const selected = [];
  let {
    multiple,
    joinValues,
    delimiter,
    formItemValue
  } = component.props;

  multiple = multiple || component.multiple;

  value.forEach(item => {
    let a = item[name];
    if (!a) return;

    if (multiple) {
      a = joinValues && typeof a === 'string' ? a.split(delimiter) : a;
      a.forEach(b => selected.push(joinValues ? b : b.value))
    } else {
      selected.push(joinValues ? a : a.value);
    }
  });

  if (!ignoreSelf && formItemValue) {
    let b = formItemValue;

    if (multiple) {
      b = joinValues && typeof b === 'string' ? b.split(delimiter) : b;
      b.forEach(b => {
        let idx = selected.indexOf(joinValues ? b : b.value);
        ~idx && selected.splice(idx, 1);
      })
    } else {
      let idx = selected.indexOf(joinValues ? b : b.value);
      ~idx && selected.splice(idx, 1);
    }
  }

  return options.filter(item => !~selected.indexOf(item.value));
};


@FormItem({
  type: 'combo'
})
@DropTarget(CARD_TYPE, {
  drop() {
  }
}, connect => ({
  connectDropTarget: connect.dropTarget()
}))
export default class FormItemCombo extends PureComponent {
  static propTypes = {
    className: PropTypes.string,
    formMode2: PropTypes.string,
    formHorizontal: PropTypes.object,
    inputClassName: PropTypes.string,
    onRequestAction: PropTypes.func,
    form: PropTypes.object,

    flat: bool,
    joinValues: bool,
    delimiter: PropTypes.string
  };

  static defaultProps = {
    value: '',
    inlineLabelClassName: '',
    multiple: false,
    addButtonClassName: 'btn-default btn-sm rounded',
    maxLength: 0,
    minLength: 0,

    flat: false,
    joinValues: true,
    delimiter: ',',

    multiLine: false,
    formMode2: 'normal',

    dragable: false
  };

  static childContextTypes = {
    combo: PropTypes.object
  };

  values = {};
  inputs = [];
  uniqueMap = {};
  newValue = null;
  idx = 1;

  constructor(props) {
    super(props);

    this.setValue = this.setValue.bind(this);
    this.getValue = this.getValue.bind(this);

    this.state = {
      schema: this.buildSchema(props),
      addable: true,
      flat: this.judgeFlat(props),
      canChange: false,
      value: null
    };

    this.addCombo = this.addCombo.bind(this);
    this.handleComboFormChange = this.handleComboFormChange.bind(this);
    this.validate = this.validate.bind(this);
    this.registerUnique = this.registerUnique.bind(this);
    this.unRegisterUnique = this.unRegisterUnique.bind(this);
    this.getRestOptions = this.getRestOptions.bind(this);
    this.setAllUniqueOptions = this.setAllUniqueOptions.bind(this);
    this.moveRow = this.moveRow.bind(this);
    this.moveRowConfirm = this.moveRowConfirm.bind(this);
    this.findRow = this.findRow.bind(this);
    this.handleAction = this.handleAction.bind(this);
  }

  getChildContext() {
    return {
      combo: {
        registerUnique: this.registerUnique,
        unRegisterUnique: this.unRegisterUnique,
        getRestOptions: this.getRestOptions
      }
    };
  }

  componentWillMount(){
    this.props.extendValidations(this.validate);
  }

  componentDidMount() {
    this.setState({
      canChange: true
    }, () => {
      if (this.newValue) {
        this.setValue(this.newValue);
        this.newValue = null;
      }
    });
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;

    if (props.controls !== nextProps.controls) {
      this.setState({
        schema: this.buildSchema(nextProps)
      });
    }

    if (props.formItemValue !== nextProps.formItemValue) {
      this.setAllUniqueOptions(nextProps);
    }

    if (this.judgeFlat(nextProps)) {
      this.setState({flat: true});
    }
  }

  componentDidUpdate() {
    this.setState({
      canChange: true
    }, () => {
      if (this.newValue) {
        this.setValue(this.newValue);
        this.newValue = null;
      }
    });
  }

  handleAction(action, item, cb) {
    const {onRequestAction} = this.props;

    if (action.actionType === 'deleteRow') {
      const rows = this.getValue();
      const key = rows.indexOf(item);

      isEmptyObject(item) ? this.removeCombo(key) : confirm('确定要删除?')
          .then(() => this.removeCombo(key));

      return;
    }


    onRequestAction(action, item, cb, item);
  }


  setAllUniqueOptions(nextProps) {
    const uniques = Object.keys(this.uniqueMap);
    let anyEmpty = false;
    let hasOptions = false;

    uniques.forEach(name => {
      const data = this.uniqueMap[name];

      if (data.options) {
        hasOptions = true;
        data.components.forEach(component => component.setState({
          options: filterOptions(data.options, nextProps.formItemValue, name, component)
        }));
        const restOptions = data.components[0] ? filterOptions(data.options, nextProps.formItemValue, name, data.components[0], true): null;

        if (!anyEmpty && restOptions && !restOptions.length) {
          anyEmpty = true;
        }
      }
    });

    this.setState({
      canChange: false,
      addable: !(hasOptions && anyEmpty)
    });
  }

  generateId(value) {
    if (!(value instanceof Array)) {
      return value;
    }

    return value.map((item) => {
      if (item.$id === undefined) {
        item.$id = this.idx++;
      }
      return item;
    });
  }

  onSubmit() {
    this.inputs.forEach(input=>input.submit());
  }

  judgeFlat(props) {
    return toBol(props.flat) && props.controls instanceof Array && props.controls.length === 1 && toBol(props.multiple)
  }

  setValue(value) {
    const {
      joinValues,
      delimiter
    } = this.props;

    if (this.state.flat) {
      if (value && value instanceof Array) {
        value = value.map((v) => {
          return Object.values(v)[0] || '';
        });
        if (toBol(joinValues)) {
          value = value.join(delimiter);
        }

        this.props.setValue(value);
      } else {
        this.props.setValue([]);
      }
    } else {
      this.props.setValue(value);
    }
  }

  getValue() {
    const {
      joinValues,
      delimiter,
      dragable,
      multiple
    } = this.props;
    let value = this.props.getValue();
    if (this.state.flat) {
        if (toBol(joinValues) && typeof value === 'string') {
          value = value.split(delimiter);
        }

        if (value && value instanceof Array) {
          value = value.map((v) => {
            return {
              temp: v
            };
          });
        }
    }

    value = value || [];

    return dragable && toBol(multiple) ? this.generateId(value) : value;
  }

  registerUnique(name, component, options) {
    const data = this.uniqueMap[name] = this.uniqueMap[name] || {
      components: []
    };

    if (!~data.components.indexOf(component)) {
      data.components.push(component);
    }

    data.options = options || component.state.options;

    this.setAllUniqueOptions(this.props);
  }

  unRegisterUnique(name, component) {
    const data = this.uniqueMap[name];

    if (data) {
      let idx = data.components.indexOf(component);

      if (~idx) {
        data.components.splice(idx, 1);
      }

      if (!data.components.length) {
        delete this.uniqueMap[name];
      }
    }
  }

  getRestOptions(name, component) {
    const data = this.uniqueMap[name];

    if (!data) {
      return null;
    }

    const {getValue, multiple} = this.props;
    const value = getValue();

    if (!toBol(multiple)) {
      return data.options;
    }

    return filterOptions(data.options, value, name, component, false);
  }

  validate() {
    return this.inputs.every(input=> input._validate && input._validate());
  }



  addCombo() {
    let value = this.getValue() || [];

    if (!Array.isArray(value)) {
      value = isPlainObject(value) ? [value] : [];
    }
    value = value.concat();

    value.push({});
    this.setValue(value);
  }


  removeCombo(key) {
    let value = this.getValue() || [];

    if (!Array.isArray(value)) {
      value = isPlainObject(value) ? [value] : [];
    }

    value = value.concat();

    value.splice(key, 1);
    this.setValue(value);
  }

  handleComboFormChange(key, values) {
    const {
      multiple,
      dragable
    } = this.props;
    let value = this.getValue();
    let newValue;

    if (toBol(multiple)) {
      value = value || [];

      if (!Array.isArray(value)) {
        value = isPlainObject(value) ? [value] : [];
      }
      value = value.concat();

      value.splice(key, 1, assign({}, value[key], values));
      newValue = value;
    } else {
      newValue = assign({}, isPlainObject(value) ? value : {}, values);
    }

    if (!this.state.canChange) {
      this.newValue = newValue;
      return;
    }

    this.setValue(newValue);
  }

  buildSchema(props) {

    let controls = props.controls;
    if (this.judgeFlat(props)) {
      controls = [assign({}, controls[0], {name: 'temp'})];
    }

    if (toBol(props.multiLine)) {
      return {
        type: 'combo-form',
        mode: 'hbox',
        form: props.form || props.controls && {
          mode: props.formMode2,
          controls: controls
        }
      }
    }

    return {
      type: 'combo-form',
      mode: 'hbox',
      controls: controls,
      actions: [],
      form: props.form
    }
  }

  showError() {
    return this.props.isFormSubmitted() && !this.props.isValid();
  }

  findRow(id) {
    const rows = this.state.value || this.getValue();
    const item = findByAttrValue(rows, '$id', id);

    return {
      item,
      index: rows.indexOf(item)
    };
  }

  moveRow(dragId, hoverIndex, end = false) {
    const {
      index,
      item
    } = this.findRow(dragId);

    const value =  this.state.value || this.getValue() || [];

    const newValue = !end && update(value, {
      $splice: [
        [index, 1],
        [hoverIndex, 0, item]
      ]
    });

    this.setState({
      value: newValue
    });
  }

  moveRowConfirm(dragId) {
    const value = this.state.value;

    if (value) {
      setTimeout(() => {
        this.setValue(value);
        this.setState({
          value: null
        });
      }, 200);
    }
  }

  renderInput() {
    const {
      $path,
      multiple,
      disabled,
      addButtonClassName,
      maxLength,
      locals,
      data,
      validateComponent,
      minLength,
      removeAble,
      inputClassName
    } = this.props;
    const schema = this.state.schema;
    const addable = this.state.addable;
    const inputs = this.inputs;
    let value = this.state.value || this.getValue();

    const dragable = this.props.dragable && toBol(multiple);

    inputs.length = 0;
    if (!schema) {
      return;
    }

    if (toBol(multiple)) {

      value = value || [];
      let length = value && value.length;
      return (
        <div key="input" className={cx('combo-control', inputClassName)}>
          {Array.isArray(value) && length ? (
            <div className={cx('combo-list m-b', schema.form ? 'm-t-n' : '')}>
              {value.map((item, key) => {
                return <div key={dragable ? item.$id : key}>
                  {
                    render(`${$path}/${schema.type}`, schema, {
                      className: schema.form ? 'm-b m-t':'m-b-xs',
                      onChange: this.handleComboFormChange.bind(this, key),
                      removeAble: typeof removeAble === 'boolean' ? removeAble : (!minLength || minLength < length),
                      onRemove: () => {
                        isEmptyObject(item) ? this.removeCombo(key) : confirm('确定要删除?')
                          .then(() => this.removeCombo(key))
                      },
                      ref: input => {input && inputs.push(input)},
                      data: item,
                      dragable,
                      disabled: disabled,
                      locals: assign({_index: key}, locals, data),
                      validateComponent,
                      moveRow: this.moveRow,
                      findRow: this.findRow,
                      moveRowConfirm: this.moveRowConfirm,
                      index: item.$id,
                      onRequestAction: this.handleAction
                    })
                  }
                  {
                    key == length || !schema.form ? null : <div className="line b-light line-lg"></div>
                  }
                </div>
              })}
            </div>
          ) : null}
          {this.props.addable !== false && (addable && (!maxLength || length < maxLength)) ? (
            <div>
              <button
                type="button"
                onClick={this.addCombo}
                className={cx('btn', addButtonClassName)}
                disabled={disabled}
              >
                <i className="fa fa-plus" />
              </button>
            </div>) : null
          }
        </div>
      );
    }

    return render(`${$path}/${schema.type}`, schema, {
      key: 'input',
      onChange: this.handleComboFormChange.bind(this, 0),
      ref: input => {input && inputs.push(input)},
      disabled: disabled,
      data: value,
      locals: assign({}, locals, data),
      validateComponent,
      onRequestAction: this.handleAction
    });
  }

  render() {
    return this.renderInput();
  }
}
